iT邦幫忙

2025 iThome 鐵人賽

DAY 10
0
AI & Data

雲端情人 - AI 愛系列 第 10

Day 10 你的小祕書 – HER不只聊天,還會幫你「做事」 :LLM Tools / Function Calling

  • 分享至 

  • xImage
  •  

LLM Tools / Function Calling / Intent Routing with FastAPI

昨天把人設與互動選單做起來後,今天要讓 AI 雲端情人真正「長手腳」:會去查即時資料(股匯金)、跑小計算、叫用你寫好的服務。做法有兩種:
• 做法 A(最通用):Intent Router —— 先讓 LLM 判斷「需求意圖」,再由後端呼叫對應工具。
• 做法 B(可選):Function Calling / JSON 模式 —— 讓 LLM 直接輸出結構化參數,後端據此執行工具。

🧠 觀念:把能力抽成「工具盒」(Function Box)

把「查金價、股市、天氣、彩券、翻譯、摘要、算式 …」都做成可重用的工具,每個工具只負責一件小事,介面長這樣:

輸入:標準化參數(symbol, city, date, …)
輸出:標準化結果(result, unit, ts, source, …)

前台只要給 LLM 一句話:「幫我看台股大盤」,Intent Router 會決定叫哪個工具,怎麼把回答「包裝成女友口吻」。

🧱 架構圖

https://ithelp.ithome.com.tw/upload/images/20250903/20112100f0TOa3c9Bt.png

✅ 做法 A:Intent Router(最穩、相依最少)

步驟 1:讓 LLM 說出「我要哪個工具」與必要參數(JSON)

# intent_router.py
from typing import Literal, Optional, Dict
from openai import OpenAI
import os, json

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"), base_url="https://free.v36.cm/v1")

# 你可以把這些 tool 名稱對應到你現有的 my_commands 模組
ToolName = Literal["stock", "forex", "gold", "weather", "lottery", "translate", "summary", "calc", "none"]

SYSTEM = (
  "你是意圖分類器,請輸出 JSON,字段固定:"
  '{"tool":"stock|forex|gold|weather|lottery|translate|summary|calc|none",'
  '"args":{...},"reason":"簡述"}。不要輸出多餘文字。'
)

def classify_intent(utterance: str) -> Dict:
    msg = [
        {"role":"system","content": SYSTEM},
        {"role":"user","content": f"句子:{utterance}。請輸出 JSON。"}
    ]
    resp = client.chat.completions.create(
        model="gpt-4o-mini", messages=msg, temperature=0, max_tokens=200
    )
    raw = resp.choices[0].message.content or "{}"
    try:
        return json.loads(raw)
    except Exception:
        return {"tool":"none","args":{},"reason":"parse_error"}

步驟 2:在主流程把結果路由到工具

# 在你的 app_fastapi.py 的某個 util 區域
from intent_router import classify_intent
from my_commands.stock.stock_gpt import stock_gpt
from my_commands.gold_gpt import gold_gpt
from my_commands.money_gpt import money_gpt
from my_commands.weather_gpt import weather_gpt
from my_commands.lottery_gpt import lottery_gpt

def call_tool(tool: str, args: dict) -> str:
    try:
        if tool == "stock":
            sym = args.get("symbol") or args.get("ticker") or "大盤"
            return stock_gpt(sym)
        if tool == "forex":
            code = (args.get("code") or "USD").upper()
            return money_gpt(code)
        if tool == "gold":
            return gold_gpt()
        if tool == "weather":
            city = args.get("city") or "桃園市"
            return weather_gpt(city)
        if tool == "lottery":
            game = args.get("game") or "大樂透"
            return lottery_gpt(game)
        if tool == "translate":
            text = args.get("text") or ""
            return f"【翻譯】\n{text}\n(此處可接你自己的翻譯器)"
        if tool == "summary":
            text = args.get("text") or ""
            return f"【摘要】\n{text[:200]}..."
        if tool == "calc":
            expr = args.get("expr") or ""
            try:
                # 安全性:避免 eval,這裡只做簡單四則
                import ast, operator as op
                return f"結果:{safe_eval(expr)}"
            except Exception:
                return "算式不合法或過於複雜。"
    except Exception as e:
        return f"工具執行失敗:{e}"
    return "目前還不會這件事。"

📌 好處
• 你的工具是你可控的(權限、速率、快取…都在你這邊),而不是把網址交給 LLM。
• 可以逐步加能力,不會把主對話搞得很複雜。
• Groq / OpenAI 任選,不依賴特定的 Function-Calling 規格。

🔁 做法 B(可選):Function Calling/JSON 模式

如果你偏好讓 LLM 直接產生「要叫的函式與參數」,可以用「JSON 嚴格模式」或「function-calling 風格的 system」。
重點是一樣:產出結構化參數 → 你來實際叫工具。
(Groq 目前以文字 JSON 最穩;OpenAI 有 tools/JSON mode 可用。)

💻 串進主流程(與 Day 8 情緒、Day 9 人設相容)

在 handle_message() 的「一般聊天」分支裡,加入這段:
(順序:判斷內建指令 → 否則做 intent classify → 呼叫工具 → 再把結果交給「人設 + 情緒」包裝)

# ……略(判斷內建指令/股票代碼之後)
else:
    # 1) 意圖分類
    intent = classify_intent(processed_msg)
    tool = intent.get("tool","none")
    args = intent.get("args",{})

    # 2) 真的去執行工具
    tool_result = call_tool(tool, args) if tool != "none" else None

    if tool_result:
        # 3) 注入人設與情緒,生成「女友口吻」回覆
        sentiment = await analyze_sentiment(processed_msg)
        # 把工具結果與使用者問題一起給 LLM,讓它包裝成對話語氣
        messages = conversation_history[user_id][-MAX_HISTORY_LEN:] + [
            {"role":"user","content": f"原始問題:{processed_msg}"},
            {"role":"assistant","content": f"工具結果(給你參考、請改寫成口語):\n{tool_result}"}
        ]
        reply_text = await get_reply_with_persona_and_sentiment(user_id, messages, sentiment)
    else:
        # 沒工具命中:走原本聊天
        sentiment = await analyze_sentiment(processed_msg)
        reply_text = await get_reply_with_persona_and_sentiment(
            user_id, conversation_history[user_id][-MAX_HISTORY_LEN:], sentiment
        )

🧪 測試腳本(Smoke Test)

# day10_smoketest.py
from intent_router import classify_intent

tests = [
    "幫我看台股大盤",
    "今天台北天氣如何?",
    "美元對台幣現在多少?",
    "明天大樂透開幾點?",
    "請把這段變成英文:早安,今天天氣不錯。",
    "2*(3+7)-5 等於?",
]

for t in tests:
    j = classify_intent(t)
    print(f"{t} -> {j}")

看輸出是否長這樣:

{"tool":"stock","args":{"symbol":"大盤"},"reason":"股市行情"}
{"tool":"weather","args":{"city":"台北"},"reason":"天氣查詢"}
{"tool":"forex","args":{"code":"USD"}}
{"tool":"lottery","args":{"game":"大樂透"}}
{"tool":"translate","args":{"text":"早安,今天天氣不錯。"}}
{"tool":"calc","args":{"expr":"2*(3+7)-5"}}

🧯 實戰細節
• 速率與快取:同一請求(同字串)在 60 秒內不要重查;股票/金價/匯率可加記憶體快取。
• 超時與退級:工具最多 3 秒;失敗就回一版「道歉 + 已改走靜態摘要」;LLM 也要準備 fallback(Groq↔OpenAI)。
• 輸入驗證:特別是 calc 類型,不要用 eval。
• 資料可信度:回答裡清楚標示「資料來源/時間」,避免誤導。
• 長度控制:先讓工具輸出結構化簡報,再由 LLM 用人設重寫成 3–6 句口語。
• 觀測:log 你每次意圖判斷與實際叫到的工具,便於調整 few-shot。

🎯 成果

到 Day 10,AI 雲端情人具備:
• 會看心情(情感分析)
• 有人格,有互動選單(人設 + Flex Menu)
• 會做事(Intent Router / Tools):看盤、匯率、金價、天氣、彩券、翻譯、小計算…
• 全流程仍口吻一致:先完成事,再讓 LLM 用女友風格說給你聽。

https://ithelp.ithome.com.tw/upload/images/20250903/20112100p7vD1IxA9L.png


上一篇
Day 9:可甜可鹹 — COSPlay角色扮演 切換人設讓 女友更有靈魂個性
下一篇
Day 11 的 her做我的小祕書 : 記事提醒
系列文
雲端情人 - AI 愛13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言